﻿using PC.PickleJar.Attributes;
using PC.PickleJar.Configuration;
using PC.PickleJar.Helpers;
using PC.PickleJar.Validators;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace PC.PickleJar
{
	public class PickleJarEngine
	{
		public PickleJarEngine()
		{
		}

		public TestMethodValidationResult ValidateTestMethod(MethodInfo testMethodInfo)
		{
			if (testMethodInfo == null)
			{
				throw new ArgumentNullException();
			}

			TestMethodValidationResult result = TestMethodValidationResult.SuccessfulResult;

			var attributes = testMethodInfo.GetCustomAttributes(typeof(SourceItemAttribute), false).Cast<SourceItemAttribute>().ToList();
			var timestampAttribute = (ItemTimestampAttribute)testMethodInfo.GetCustomAttributes(typeof(ItemTimestampAttribute), false).SingleOrDefault();

			if (attributes.Count > 0)
			{
				this.PerformItemTimestampPreValidation(testMethodInfo, timestampAttribute);

				PickleJarEngine.ProcessSourceItemAttributes(attributes, timestampAttribute.Timestamp, ref result);
			}

			return result;
		}

		private void PerformItemTimestampPreValidation(MethodInfo testMethodInfo, ItemTimestampAttribute timestampAttribute)
		{
			if (timestampAttribute == null)
			{
				throw new InvalidOperationException(
					String.Format(
						CultureInfo.InvariantCulture,
						"A {0} must be specified on method {1}.",
						typeof(ItemTimestampAttribute).Name,
						testMethodInfo.Name));
			}

			if (timestampAttribute.Timestamp > DateTime.Now)
			{
				throw new InvalidOperationException(@"Timestamp appears to be in the future :\");
			}
		}

		private static void ProcessSourceItemAttributes(IEnumerable<SourceItemAttribute> attributes, DateTime timestamp, ref TestMethodValidationResult result)
		{
			var configurationHelper = new ConfigurationHelper();
			
			var validators = configurationHelper.Validators;
			List<ValidationResult> validationResults = new List<ValidationResult>();

			foreach (var sourceItem in attributes)
			{
				var applicableValidators = validators.Where(validator => validator.TargetAttributeTypes.Contains(sourceItem.GetType())).ToList();
				validationResults.AddRange(applicableValidators.Select(validator => validator.Validate(sourceItem, timestamp)));
			}

			var offenders = validationResults.Where(item => item.RequiresAttention);
			if (offenders.Count() > 0)
			{
				String message = String.Join(Environment.NewLine, offenders.Select(item => item.Reason));
				result = new TestMethodValidationResult(ResultEnum.Failure, message, configurationHelper);
			}
		}
	}
}
